/**
 *  IMPORTANT: The following type definitions should be kept consistent
 *             with the `stdlib.go` file.
 */

// TODO: split this file
type ProjRef [out T, C]
    &(Maybe[C]) => (T, C);
type CaseRef [out T, C]
    &(Maybe[C]) => (T, Maybe[C]);
export function -> :
    [T,C] &(ProjRef[T,C]) => C
    &(f) => let (_, c) := { f None }, c;
export function -> :
    [T,C] &(CaseRef[T,C]) => Maybe[C]
    &(f) => let (_, c?) := { f None }, c?;
export function <- :
    [T,C] &(ProjRef[T,C], C) => T
    &(f,c) => let (t, _) := { f { Some c } }, t;
export function <- :
    [T,C] &(CaseRef[T,C], C) => T
    &(f,c) => let (t, _) := { f { Some c } }, t;

// Basic Sum Types
type Bool enum {
    type Yes;
    type No;
};
export const Yes: Bool := { |Bool| { |Yes| () } };
export const No: Bool := { |Bool| { |No| () } };
type Maybe [out T] enum {
    type Some weak T;
    type None;
};
export function Some:
    [T] &(T) => Maybe[T]
    &(t) => { |Maybe| { |Some| t } };
export const None: Maybe[never] := { |Maybe| { |None[never]| () } };
type Result [out T, out E] enum {
    type Success weak T;
    type Failure weak E;
};
export function Success:
    [T] &(T) => Result[T,never]
    &(t) => { |Result| { |Success[T,never]| t } };
export function Failure:
    [T] &(T) => Result[never,T]
    &(t) => { |Result| { |Failure[never,T]| t } };
type Ordering enum {
    type <<;
    type ==;
    type >>;
};
export function =:
    &(Bool,Bool) => Bool
    native 'enum-index-equal';
export function =:
    &(Ordering,Ordering) => Bool
    native 'enum-index-equal';

type Optional[out T] Maybe[T];
export const @default: Optional[never] := { |Optional| None };

export function assume:[T]
    &((Bool,T), &() => T) => T
    &(input, k) =>
        let (cond, fallback) := input,
        if cond:
            { k () },
        else:
            fallback;

// ----------

export function map:[A,B,E]
    &(Result[A,E], &(A) => B) => Result[B,E]
    &(ra, f) =>
        switch ra:
        case Success a:
            { Success { f(a) } },
        case Failure err:
            { Failure err },
        end;

// ----------

// Functor(Maybe)
export function map:[A,B]
    &(Maybe[A], &(A) => B) => Maybe[B]
    &(v?, f) =>
        switch v?:
        case Some v:
            { Some { f v } },
        case None:
            None,
        end;

// Monad(Maybe)
export function get:[A,B]
    &(Maybe[A], &(A) => Maybe[B]) => Maybe[B]
    &(v?, k) =>
        switch v?:
        case Some v:
            { k v },
        case None:
            None,
        end;

// Applicative(Maybe)
export function Maybe:[A,B,T]
    &((Maybe[A],Maybe[B]), &(A,B) => T) => Maybe[T]
    &(pair, f) =>
        let (a?, b?) := pair,
        \ a := get a?,
        \ b := get b?,
        { Some { f(a,b) } };

export function Maybe:[A,B,C,T]
    &((Maybe[A],Maybe[B],Maybe[C]), &(A,B,C) => T) => Maybe[T]
    &(triple, f) =>
        let (a?, b?, c?) := triple,
        \ a := get a?,
        \ b := get b?,
        \ c := get c?,
        { Some { f(a,b,c) } };

export function consume:[A,T]
    &((Maybe[A],T), &(A) => T) => T
    &(input, f) =>
        let (v?, fallback) := input,
        switch v?:
        case Some v:
            { f(v) },
        case None:
            fallback,
        end;

export function ??:[T]
    &(Maybe[T], T) => T
    &(t?, fallback) =>
        switch t?:
        case Some t:
            t,
        case None:
            fallback,
        end;

export function Bool:[T]
    &(Maybe[T]) => Bool
    &(t?) =>
        switch t?:
        case Some _:
            Yes,
        case None:
            No,
        end;
